package com.neu.filter;


import com.neu.base.AuthContants;
import com.neu.base.Result;
import com.neu.entity.security.LoginUser;
import com.neu.utils.JwtUtils;
import com.neu.utils.RedisUtil;
import com.neu.utils.ServletUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;


import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;



@Slf4j
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    private RedisUtil redisUtils;

    private JwtUtils jwtUtils;

    private Long expiration;

    // 定义无需认证的路径
    private static final List<String> EXCLUDE_URLS = Arrays.asList(
            "/auth/login",
            "/auth/register",
            "/auth/captcha",
            "/swagger-resources/**",
            "/v3/**",
            "/webjars/**",
            "/doc.html",
            "/captcha/**",
            "/favicon.ico",
            "/sse/**",
            "/minio/upload"

    );
    public JwtAuthenticationTokenFilter( RedisUtil redisUtils, JwtUtils jwtUtils,Long expiration) {
        this.redisUtils = redisUtils;
        this.jwtUtils = jwtUtils;
        this.expiration = expiration;

    }
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String path = request.getRequestURI();
        log.info("path = " + path);
        // 如果请求路径无需认证，直接放行，例如swagger请求，登录请求等
        if (EXCLUDE_URLS.stream().anyMatch(pattern -> new AntPathMatcher().match(pattern, path))) {
            filterChain.doFilter(request, response);
            return;
        }

        String token = request.getHeader("Authorization");
        //但是在这里，对于不会携带token的请求，我们应该返回401状态码，而不是直接放行
        //对于 无状态认证，后端应该 始终通过请求中的 token 进行认证，而不是进入下一层过滤器通过SecurityContext认证
        if (StringUtils.hasText(token)) {
            //判断token是否过期 看看能不能从redis里面取出来
            if (jwtUtils.isTokenExpired(token)) {
                log.warn("Access Token 已过期");
                // 返回提示客户端重新登录
                ServletUtils.renderResult(HttpStatus.UNAUTHORIZED,response, Result.error("Access Token 已过期，请重新登录"));
                return;
            }
            // 获取 Token 剩余时间
            long remainingTime = jwtUtils.getRemainingTime(token);
            log.info("Access Token 剩余时间: {} 秒", remainingTime/1000);

            // 如果剩余时间低于阈值，生成新的 Token
            if (remainingTime < AuthContants.REFRESH_TOKEN_THRESHOLD) {
                String username = jwtUtils.getClaims(token).getSubject();
                String newAccessToken = jwtUtils.generateToken(username);
                // 将新的 Token 添加到响应头中，前端可以从响应头中获取并更新
                response.setHeader("Authorization", newAccessToken);
                //刷新redis
                redisUtils.set(AuthContants.REDIS_KEY_AUTH_TOKEN + username, newAccessToken,expiration);
                redisUtils.set(AuthContants.REDIS_KEY_AUTH_USER_DETAIL + username, redisUtils.get(AuthContants.REDIS_KEY_AUTH_USER_DETAIL + username),expiration);
                log.info("生成新的 Access Token，并添加到响应头:"+newAccessToken);
            }

            // 解析token获取用户id
            String username = jwtUtils.getClaims(token).getSubject();
            // 从redis中获取用户信息
            LoginUser loginUser = (LoginUser) redisUtils.get(AuthContants.REDIS_KEY_AUTH_USER_DETAIL + username);
            if (loginUser == null) {
                log.warn("Redis 中不存在用户信息");
                ServletUtils.renderResult(HttpStatus.UNAUTHORIZED,response, Result.error("Access Token 已过期，请重新登录"));
                return;
            }

            //需要将 Authentication 对象设置到 SecurityContext 中。如果没有正确设置，Spring Security 会认为请求未经过身份验证。
            // 将用户信息存入上下文 principal: 用户信息   credentials: 密码   authorities: 权限
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, loginUser.getPassword(), loginUser.getAuthorities());
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }else{
            ServletUtils.renderResult(HttpStatus.UNAUTHORIZED,response, Result.error("未认证，请先登录！"));
            return;
        }

        filterChain.doFilter(request, response);

    }
}
